(function (g) {
  const toString = function toString(t) {
    return Function.prototype.toString.call(t);
  };
  const getId = function getId() {
    return (+new Date()).toString(32);
  };
  const proxy = function proxy(target, scripts) {
    if (scripts && scripts.length) importScripts.apply(self, scripts);
    let instance;
    self.onmessage = function (ev) {
      const { type, data, id } = ev.data;
      let rtn = null;
      if (type === 'exec') {
        rtn = target.apply(null, data);
      } else if (type === 'create') {
        instance = new target(...data);
      } else if (type === 'get') {
        rtn = instance;
        for (let p of data) {
          rtn = rtn[p];
        }
      } else if (type === 'apply') {
        rtn = instance;
        for (let p of data.path) {
          rtn = rtn[p];
        }
        rtn = rtn.apply(instance, data.data);
      }
      self.postMessage({
        id,
        type,
        data: rtn,
      });
    };
  };

  const _proxy = function _proxy(worker, path) {
    path = path || [];
    return new Proxy(function(){}, {
      get: (_, prop, receiver) => {
        if (prop === 'then') {
          if (path.length === 0) return null;
          const p = worker.send({
            type: 'get',
            data: path,
          });
          return p.then.bind(p);
        }
        return _proxy(worker, path.concat(prop));
      },
      apply: (_0, _1, args) => {
        return worker.send({
          type: 'apply',
          data: {
            path,
            data: args,
          },
        });
      },
    });
  };

  const workify = function workify(target, scripts) {
    const code = `(${toString(proxy)})(${toString(target)}, ${JSON.stringify(scripts)})`;
    const blob = new Blob([code]);
    const url = URL.createObjectURL(blob);
    const worker = new Worker(url);
    worker._cbs = {};
    worker.onmessage = function (ev) {
      const { type, id, data } = ev.data;
      if (type === 'exec') {
        worker._cbs[id](data);
      } else if (type === 'create') {
        worker._cbs[id](_proxy(worker));
      } else if (type === 'apply') {
        worker._cbs[id](data);
      } else if (type === 'get') {
        worker._cbs[id](data);
      }
    };
    worker.send = function ({ type, data }) {
      return new Promise((res) => {
        const id = getId();
        worker._cbs[id] = (data) => {
          res(data);
        };
        worker.postMessage({
          id,
          type,
          data,
        });
      });
    }
    const rtn = function rtn(...args) {
      if (this instanceof rtn) {
        return worker.send({
          type: 'create',
          data: args,
        });
      } else {
        return worker.send({
          type: 'exec',
          data: args,
        });
      }
    };
    return rtn;
  };
  g.workify = workify;
})(window);
